/*
 * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.baomidou.mybatisplus.extension.conditions.query;

import static com.baomidou.mybatisplus.core.enums.SqlKeyword.AND;
import static com.baomidou.mybatisplus.core.enums.SqlKeyword.BETWEEN;
import static com.baomidou.mybatisplus.core.enums.SqlKeyword.NOT_BETWEEN;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.ibatis.reflection.property.PropertyNamer;

import com.baomidou.mybatisplus.core.conditions.AbstractJoinWrapper;
import com.baomidou.mybatisplus.core.conditions.SharedString;
import com.baomidou.mybatisplus.core.conditions.query.Query;
import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments;
import com.baomidou.mybatisplus.core.enums.BaseFuncEnum;
import com.baomidou.mybatisplus.core.enums.SqlKeyword;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.conditions.query.interfaces.JoinCompareFun;
import com.baomidou.mybatisplus.extension.conditions.query.interfaces.LambdaJoinFunc;


/**
 * 本类使用SFunction来构造多表查询条件
 * demo:--需要注意的是 如果要添加其他表的过滤条件或者排序字段，请在调用了join方法后直接调用类似下面这种写法，否则编译不通过
 * LambdaJoinQueryWrapper<User> wrapper = new LambdaJoinQueryWrapper<>(User.class);
 * wrapper.eq(User::getSchoolId,1);
 * wrapper.innerJoin(School.class).like(School::getSchoolName,"一");
 * mapper.selectJoinList(wrapper);
 *
 * @author wanglei
 * @since 2022-03-17
 */
public class LambdaJoinQueryWrapper<T> extends AbstractJoinWrapper<T, SFunction<T, ?>,
    LambdaJoinQueryWrapper<T>> implements LambdaJoinFunc<T>, Query<LambdaJoinQueryWrapper<T>, T, SFunction<T, ?>>, JoinCompareFun<LambdaJoinQueryWrapper<T>, T> {

    /**
     * 主表model的class
     */
    private Class<?> mainClass;

    public LambdaJoinQueryWrapper(Class<?> mainClass) {
        super(mainClass);
        this.mainClass = mainClass;
        super.initNeed();
    }


    /**
     * 非对外公开的构造方法,只用于生产嵌套 sql
     *
     * @param entityClass 本不应该需要的
     */
    private LambdaJoinQueryWrapper(Class<?> mainClass, T entity, Class<T> entityClass, AtomicInteger paramNameSeq,
                                   Map<String, Object> paramNameValuePairs, MergeSegments mergeSegments, SharedString paramAlias,
                                   SharedString lastSql, SharedString sqlComment, SharedString sqlFirst, StringBuilder joinFroms,Map<Class<?>, String> aliasMap) {
        super(mainClass);
        super.setEntity(entity);
        super.setEntityClass(entityClass);
        this.mainClass = mainClass;
        this.paramNameSeq = paramNameSeq;
        this.paramNameValuePairs = paramNameValuePairs;
        this.expression = mergeSegments;
        this.paramAlias = paramAlias;
        this.lastSql = lastSql;
        this.sqlComment = sqlComment;
        this.sqlFirst = sqlFirst;
        this.joinFroms = joinFroms;
        this.aliasMap = aliasMap;
    }

    @Override
    protected LambdaJoinQueryWrapper<T> instance() {
        return new LambdaJoinQueryWrapper<>(mainClass, getEntity(), getEntityClass(), paramNameSeq, paramNameValuePairs, new MergeSegments(),
            paramAlias, SharedString.emptyString(), SharedString.emptyString(), SharedString.emptyString(), this.joinFroms,this.aliasMap);
    }

    @Override
    public <X, J> LambdaJoinQueryWrapper<X> join(Class<X> joinClass, SFunction<X, ?> leftField, SFunction<J, ?> rightField, String joinType) {
        pubJoin(joinClass, leftField, rightField, joinType);
        return (LambdaJoinQueryWrapper<X>) this;
    }

    public <X> LambdaJoinQueryWrapper<X> end(Class<X> clz) {
        return (LambdaJoinQueryWrapper<X>) this;
    }

    @Override
    protected <LEFT, RIGHT> String customBuildJoin(LEFT leftProperty, RIGHT rightProperty) {
        LambdaMeta leftMeta = LambdaUtils.extract((SFunction<?, ?>) leftProperty);
        LambdaMeta rightMeta = LambdaUtils.extract((SFunction<?, ?>) rightProperty);

        // 关联表别名
        String leftAlias = aliasMap.get(leftMeta.getInstantiatedClass());
        // 关联表别名
        String rightAlias = aliasMap.get(rightMeta.getInstantiatedClass());

        getCache(leftMeta.getInstantiatedClass(), PropertyNamer.methodToProperty(leftMeta.getImplMethodName()));

        return Constants.SPACE + leftAlias + Constants.DOT + getCache(leftMeta.getInstantiatedClass(), PropertyNamer.methodToProperty(leftMeta.getImplMethodName())).getColumn()
            + Constants.EQUALS + rightAlias + Constants.DOT + getCache(rightMeta.getInstantiatedClass(), PropertyNamer.methodToProperty(rightMeta.getImplMethodName())).getColumn();
    }

    /**
     * 软删除字段处理
     *
     * @param joinClass
     */
    @Override
    protected void initLogicDelete(Class<?> joinClass, String joinType) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(joinClass);
        // 不是left join的时候
        if (tableInfo.getLogicDeleteFieldInfo() != null) {
            this.joinFroms.append(Constants.SPACE + Constants.AND + Constants.SPACE + aliasMap.get(joinClass) + Constants.DOT
                + tableInfo.getLogicDeleteFieldInfo().getColumn() + Constants.SPACE + Constants.EQUALS + Constants.SPACE +
                parseLogicNotDeleteValue(tableInfo.getLogicDeleteFieldInfo()));
        }
        //如果没有join其他的表，需要把主表的软删除字段放到on上
        if (this.aliasMap.size() == 2) {
            tableInfo = TableInfoHelper.getTableInfo(this.mainClass);
            if (tableInfo.getLogicDeleteFieldInfo() != null) {
                this.joinFroms.append(Constants.SPACE + Constants.AND + Constants.SPACE + aliasMap.get(this.mainClass) + Constants.DOT
                    + tableInfo.getLogicDeleteFieldInfo().getColumn() + Constants.SPACE + Constants.EQUALS + Constants.SPACE +
                    parseLogicNotDeleteValue(tableInfo.getLogicDeleteFieldInfo()));
            }
        }
    }


    @Override
    public MergeSegments getExpression() {
        //如果没有join其他的表，需要把主表的软删除字段放到where上
        if (this.aliasMap.size() == 1 && !isAppendMainLogicDelete) {
            isAppendMainLogicDelete = true;
            TableInfo tableInfo = TableInfoHelper.getTableInfo(this.mainClass);
            if (tableInfo.getLogicDeleteFieldInfo() != null) {
                eq(LambdaUtils.getSFunction(this.mainClass, tableInfo.getLogicDeleteFieldInfo().getPropertyType()
                        , tableInfo.getLogicDeleteFieldInfo().getProperty())
                    , parseLogicNotDeleteValue(tableInfo.getLogicDeleteFieldInfo()));
            }
        }
        return expression;
    }

    @Override
    protected String columnToString(SFunction<T, ?> column) {
        return extractColumn(column);
    }

    protected String extractColumn(SFunction<?, ?> column) {
        LambdaMeta lambdaMeta = LambdaUtils.extract(column);
        if (!super.aliasMap.containsKey(lambdaMeta.getInstantiatedClass())) {
            throw ExceptionUtils.mpe("not join class: \"%s\".", lambdaMeta.getInstantiatedClass().getName());
        }
        return super.aliasMap.get(lambdaMeta.getInstantiatedClass()) + "." + getCache(lambdaMeta.getInstantiatedClass(),
            PropertyNamer.methodToProperty(lambdaMeta.getImplMethodName())).getColumn();
    }

    @Override
    @SafeVarargs
    public final LambdaJoinQueryWrapper<T> select(SFunction<T, ?>... columns) {
        return select(Arrays.asList(columns));
    }

    public LambdaJoinQueryWrapper<T> select(List<SFunction<T, ?>> columns) {
        if (CollectionUtils.isNotEmpty(columns)) {
            for (SFunction<?, ?> column : columns) {
                LambdaMeta lambdaMeta = LambdaUtils.extract(column);
                selectColumns.add(SelectColumn.of(lambdaMeta.getInstantiatedClass(), getCache(lambdaMeta.getInstantiatedClass(),
                    PropertyNamer.methodToProperty(lambdaMeta.getImplMethodName())).getColumn()));
            }
        }
        return typedThis;
    }

    /**
     * 查询函数列,如果column不指定，则查询 *
     * <p>select xx(column) as alias</p>
     */
    @Override
    public final LambdaJoinQueryWrapper<T> selectFun(BaseFuncEnum fun, SFunction<T, ?> alias, SFunction<T, ?> column) {
        LambdaMeta aliasLambdaMeta = LambdaUtils.extract(alias);
        LambdaMeta columnLambdaMeta = column == null ? null : LambdaUtils.extract(column);
        String columnStr = column == null ? "*" : aliasMap.get(columnLambdaMeta.getInstantiatedClass()) + Constants.DOT
            + getCache(columnLambdaMeta.getInstantiatedClass(), PropertyNamer.methodToProperty(columnLambdaMeta.getImplMethodName())).getColumn();
        String funStr = String.format(fun.getSql(), columnStr);
        this.funSqlSelect.add(new SharedString(funStr + Constants.AS + PropertyNamer.methodToProperty(aliasLambdaMeta.getImplMethodName())));
        return typedThis;
    }


    @Override
    public <J> LambdaJoinQueryWrapper<T> eq(boolean condition, SFunction<T, Object> column, SFunction<J, Object> val) {
        return addCondition(condition, column, SqlKeyword.EQ, val);
    }

    @Override
    public <J> LambdaJoinQueryWrapper<T> ne(boolean condition, SFunction<T, Object> column, SFunction<J, Object> val) {
        return addCondition(condition, column, SqlKeyword.NE, val);
    }

    @Override
    public <J> LambdaJoinQueryWrapper<T> gt(boolean condition, SFunction<T, Object> column, SFunction<J, Object> val) {
        return addCondition(condition, column, SqlKeyword.GT, val);
    }

    @Override
    public <J> LambdaJoinQueryWrapper<T> ge(boolean condition, SFunction<T, Object> column, SFunction<J, Object> val) {
        return addCondition(condition, column, SqlKeyword.GE, val);
    }

    @Override
    public <J> LambdaJoinQueryWrapper<T> lt(boolean condition, SFunction<T, Object> column, SFunction<J, Object> val) {
        return addCondition(condition, column, SqlKeyword.LT, val);
    }

    @Override
    public <J> LambdaJoinQueryWrapper<T> le(boolean condition, SFunction<T, Object> column, SFunction<J, Object> val) {
        return addCondition(condition, column, SqlKeyword.LE, val);
    }

    @Override
    public <J, J2> LambdaJoinQueryWrapper<T> between(boolean condition, SFunction<T, Object> column, SFunction<J, Object> val1, SFunction<J2, Object> val2) {
        return maybeDo(condition, () -> appendSqlSegments(columnToSqlSegment(column), BETWEEN,
            () -> extractColumn(val1), AND, () -> extractColumn(val2)));
    }

    @Override
    public <J, J2> LambdaJoinQueryWrapper<T> notBetween(boolean condition, SFunction<T, Object> column, SFunction<J, Object> val1, SFunction<J2, Object> val2) {
        return maybeDo(condition, () -> appendSqlSegments(columnToSqlSegment(column), NOT_BETWEEN,
            () -> extractColumn(val1), AND, () -> extractColumn(val2)));
    }

    /**
     * 普通查询条件
     *
     * @param condition  是否执行
     * @param column     属性
     * @param sqlKeyword SQL 关键词
     * @param val        条件值
     */
    protected <J> LambdaJoinQueryWrapper<T> addCondition(boolean condition, SFunction<T, Object> column, SqlKeyword sqlKeyword, SFunction<J, Object> val) {
        return maybeDo(condition, () -> appendSqlSegments(columnToSqlSegment(column), sqlKeyword,
            () -> extractColumn(val)));
    }
}
